﻿/*************************************************************************
 * Created by OhZee Creative, LLC
 * 2013
 * This code is licensed under Microsoft Public License (Ms-PL)
 * http://opensource.org/licenses/ms-pl
 * http://ozframework.codeplex.com/
 *************************************************************************/

namespace OhZeeCreative.Helpers
{
	using System;
    using System.Runtime.CompilerServices;

    public static class MathHelper
	{
		public enum CartesianPlacement
		{
			Undefined,
			Origin,
			PositiveXAxis,
			FirstQuadrant,
			PositiveYAxis,
			SecondQuadrant,
			NegativeXAxis,
			ThirdQuadrant,
			NegativeYAxis,
			FourthQuadrant
		};

		/// <summary>
		/// Use it to get the real value using a range and a normalized value. Also see NormalValue()
		/// </summary>
		/// <param name="rangeStart"></param>
		/// <param name="rangeEnd"></param>
		/// <param name="normalizedValue">Ideally a normalized value is between 0 and 1, inclusive, but this is not required</param>
		/// <returns></returns>
		public static double ActualValue(double rangeStart, double rangeEnd, double normalizedValue)
		{
			return rangeStart + normalizedValue * (rangeEnd - rangeStart);
		}
		
		// Returns the number of deltas in the given range
		public static int DeltaCount(double rangeStart, double rangeEnd, double delta)
		{
			double range = rangeEnd - rangeStart;

			if ((Math.Sign(range) != Math.Sign(delta)) || (delta == 0.0))
			{
				return 0;
			}

			return (int)Math.Floor(range / delta);

		}

		public static double DeltaValue(double rangeStart, double rangeEnd, int numberOfDeltas)
		{
			if (numberOfDeltas == 0)
			{
                DebugHelper.Fail();
				throw new ArgumentException("OZC: DeltaValue, numberOfDeltas cannot be 0");
			}
			return (rangeEnd - rangeStart) / numberOfDeltas;
		}

#if NETFX_CORE
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public static double Halved(this double value)
		{
			return value * 0.5;
		}

#if NETFX_CORE
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
#endif
        public static double Doubled(this double value)
        {
            return value * 2.0;
        }
		
		/// <summary>
		/// Similar to Math.Max, except accepts a nullable double.
		/// </summary>
		/// <param name="current"></param>
		/// <param name="value"></param>
		/// <returns>Value if current is null, or max of the two values</returns>
		public static double Maximum(double? current, double value)
		{
			if (!current.HasValue || value > current.Value)
			{
				return value;
			}

			return current.Value;
		}

		/// <summary>
		/// Similar to Math.Min, except accepts a nullable double.
		/// </summary>
		/// <param name="current"></param>
		/// <param name="value"></param>
		/// <returns>Value if current is null, or minimum of the two values</returns>
		public static double Minimum(double? current, double value)
		{
			if (!current.HasValue || value < current.Value)
			{
				return value;
			}

			return current.Value;
		}

		/// <summary>
		/// Use it to get the normal value of a value relative to a range Also see ActualValue()
		/// </summary>
		/// <param name="rangeStart"></param>
		/// <param name="rangeEnd"></param>
		/// <param name="value"></param>
		/// <returns>A number between 0 and 1, inclusive, if the value falls within the range. Otherwise, a relative value. 0 if rangeStart == rangeEnd</returns>
		public static double NormalValue(double rangeStart, double rangeEnd, double value)
		{
			if (rangeEnd == rangeStart)
			{
				return 0;
			}

			return (value - rangeStart) / (rangeEnd - rangeStart);
		}

		public static double Degrees(double radians)
		{
			return (radians / (2 * Math.PI)) * 360.0;
		}

		public static double Radians(double degrees)
		{
			return 2 * Math.PI * (degrees / 360.0);
		}


        public static bool IsValid(this double value)
        {
            return !Double.IsNaN(value) && !Double.IsInfinity(value);
        }

		/// <summary>
		/// Ensures that the value is an actual number
		/// </summary>
		/// <param name="value"></param>
		/// <returns>The value if it's an actual number, 0 otherwise</returns>
		public static double SafeDouble(this double value)
		{
			if (!value.IsValid())
			{
				return 0.0;
			}
			return value;
		}

		public static double SafeDouble(this double value, double safeSubstitute)
		{
            if (!value.IsValid())
			{
				return safeSubstitute;
			}
			return value;
		}


		public static CartesianPlacement GetCartesianPlacement(double x, double y)
		{
			if (x == 0 && y == 0)
			{
				return CartesianPlacement.Origin;
			}

			if (x > 0 && y == 0)
			{
				return CartesianPlacement.PositiveXAxis;
			}

			if (x == 0 && y > 0)
			{
				return CartesianPlacement.PositiveYAxis;
			}

			if (x < 0 && y == 0)
			{
				return CartesianPlacement.NegativeXAxis;
			}

			if (x == 0 && y < 0)
			{
				return CartesianPlacement.NegativeYAxis;
			}

			if (x > 0 && y > 0)
			{
				return CartesianPlacement.FirstQuadrant;
			}

			if (x < 0 && y > 0)
			{
				return CartesianPlacement.SecondQuadrant;
			}

			if (x < 0 && y < 0)
			{
				return CartesianPlacement.ThirdQuadrant;
			}

			if (x > 0 && y < 0)
			{
				return CartesianPlacement.FourthQuadrant;
			}

			throw new InvalidOperationException();
		}

		/// <summary>
		/// For a given triangle ABC, where length edge1 = |AC|, edge2 = |AB| and angleA is BAC,
		/// calculates angleB = CBD, where A, B and D are on the same line.
		/// </summary>
		/// <param name="innerAngle">Defined in radians</param>
		/// <returns>The outer angle in radians</returns>
		public static double AdjustedAngle(double angle, double radius, double offsetX, double offsetY)
		{
			double x = radius * Math.Cos(angle);
			double y = radius * Math.Sin(angle);
			double deltaX =  x - offsetX;
			double deltaY = y - offsetY;

			CartesianPlacement placement = GetCartesianPlacement(deltaX, deltaY);

			switch (placement)
			{
				case CartesianPlacement.Origin: return angle;
				case CartesianPlacement.PositiveXAxis: return Math.PI * 0.0;
				case CartesianPlacement.PositiveYAxis: return Math.PI * 0.5;
				case CartesianPlacement.NegativeXAxis: return Math.PI * 1.0;
				case CartesianPlacement.NegativeYAxis: return Math.PI * 1.5;				
			}

			DebugHelper.Assert(deltaX != 0);
			// atan will always have a value (-pi/2, pi/2), non inclusive
			double atan = Math.Atan(deltaY / deltaX);

			switch (placement)
			{
				case CartesianPlacement.FirstQuadrant: DebugHelper.Assert(atan > 0); return atan;
				case CartesianPlacement.SecondQuadrant: DebugHelper.Assert(atan < 0); return Math.PI + atan;
				case CartesianPlacement.ThirdQuadrant: DebugHelper.Assert(atan > 0); return Math.PI + atan;
				case CartesianPlacement.FourthQuadrant: DebugHelper.Assert(atan < 0); return 2 * Math.PI + atan;
			}

			throw new InvalidOperationException();
		}

		public static double Hypotenuse(double x, double y)
		{
			return Math.Sqrt(x * x + y * y);
		}
	}
}
